
// Copyright (C) 2007 Richard Uhler
// This file is part of Esiply.
//
// Esiply is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License, Version 2,
// as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

// This file contains the implementation for all the Math standard
// library stuff that can be implemented completely in javascript

// Ecma 15.8 Math
Esiply.set_attributes(this, "Math", Esiply.DONT_ENUM);
Math = new Object();
Esiply.set_class(Math, "Math");

// Ecma 15.8.1.1 E
// The number value for e, the base of the natrual logarithms, which
// is approximately 2.7182818284590452354
Math.E = 2.7182818284590452354;
Esiply.set_attributes(Math, "E",
        Esiply.DONT_ENUM | Esiply.DONT_DELETE | Esiply.READ_ONLY);

// Ecma 15.8.1.2 LN10
// The number value for the natural logarithm of 10, which is approximately
// 2.302585092994046
Math.LN10 = 2.302585092994046;
Esiply.set_attributes(Math, "LN10",
        Esiply.DONT_ENUM | Esiply.DONT_DELETE | Esiply.READ_ONLY);

// Ecma 15.8.1.3 LN2
// The number value for the natural logarithm of 2, which is approximately
// 0.6931471805599453
Math.LN2 = 0.6931471805599453;
Esiply.set_attributes(Math, "LN2",
        Esiply.DONT_ENUM | Esiply.DONT_DELETE | Esiply.READ_ONLY);

// Ecma 15.8.1.4 LOG2E
// The number value for the base-2 logarithm of e, the base of the natural
// logarithms; this value is approximately 1.4426950408889634
Math.LOG2E = 1.4426950408889634;
Esiply.set_attributes(Math, "LOG2E",
        Esiply.DONT_ENUM | Esiply.DONT_DELETE | Esiply.READ_ONLY);

// Ecma 15.8.1.5 LOG10E
// The number value for the base-10 logarithm of e, the base of the natural
// logarithms; this value is approximately 0.4342944819032518
Math.LOG10E = 0.4342944819032518;
Esiply.set_attributes(Math, "LOG10E",
        Esiply.DONT_ENUM | Esiply.DONT_DELETE | Esiply.READ_ONLY);

// Ecma 15.8.1.6 PI
// The number value for pi, the ratio of the circumference of a circle to
// its diameter, which is approximately 3.1415926535897932.
Math.PI = 3.1415926535897932;
Esiply.set_attributes(Math, "PI",
        Esiply.DONT_ENUM | Esiply.DONT_DELETE | Esiply.READ_ONLY);

// Ecma 15.8.1.7 SQRT1_2
// The number value for the square root of 1/2, which is approximately
// 0.7071067811865476.
Math.SQRT1_2 = 0.7071067811865476;
Esiply.set_attributes(Math, "SQRT1_2",
        Esiply.DONT_ENUM | Esiply.DONT_DELETE | Esiply.READ_ONLY);

// Ecma 15.8.1.8 SQRT2
// The number value for the square root of 2, which is approximately 
// 1.4142135623730951.
Math.SQRT2 = 1.4142135623730951;
Esiply.set_attributes(Math, "SQRT2",
        Esiply.DONT_ENUM | Esiply.DONT_DELETE | Esiply.READ_ONLY);

// Ecma 15.8.2.1 abs(x)
// Returns the absolute value of x; the result has the same magnitude as
// x but has positive sign
Esiply.set_attributes(Math, "abs", Esiply.DONT_ENUM);
Math.abs = function(x)
{
    return x < 0 ? -1 * x : x;
};

// Ecma 15.8.2.2 acos(x)
// Returns an implementation-dependent approximation to the arc cosine
// of x. The result is expressed in radians and ranges from +0 to +pi
Esiply.set_attributes(Math, "acos", Esiply.DONT_ENUM);
Math.acos = function(x)
{
    return Math.PI/2 - Math.asin(x);
};

// Ecma 15.8.2.3 asin(x)
// Returns an implementation-dependent approximation to the arc sine of x.
// The result is expressed in radians and ranges from -pi/2 to pi/2
Esiply.set_attributes(Math, "asin", Esiply.DONT_ENUM);
Math.asin = function(x)
{
    if (x > 1 || x < -1)
    {
        return NaN;
    }

    // The taylor series expansion is way too slow (over 8 hours for one
    // simple calculation on my computer). So instead here is an attempt
    // at a bisection method.

    var ls = -Math.PI/2;
    var rs = Math.PI/2;
    var threshold = 1e-10;

    // We know the answer is somewhere between ls and rs.
    while (rs - ls > threshold)
    {
        debug("asin: ls: " + ls + ", rs: " + rs + "\n");

        // Try the midpoint
        var mid = (ls+rs)/2;
        if (Math.sin(mid) < x)
        {
            ls = mid;
        }
        else
        {
            rs = mid;
        }
    }
    return ls;
};

// Ecma 15.8.2.4 atan(x)
// Returns an implementation-dependent approximation to the arc tangent
// of x. The result is expressed in radians and ranges from -pi/2 to pi/2
Esiply.set_attributes(Math, "atan", Esiply.DONT_ENUM);
Math.atan = function(x)
{
    if (Math.abs(x) < 1)
    {
        // for |x| < 1
        // atan is approximately
        // x-x^3/3 +x^5/5-x^7/7...
        var res = x;
        var add = x;
        var last = NaN;
        for (var i = 3; res != last; i += 2)
        {
            last = res;
            add *= -x*x*(i-2)/i;
            res += add;
            debug("atan: at: " + res + "\n");
        }
        return res;
    }

    // Otherwise we have +-pi/2 - (1/x-1/(3x^3) +1/(5x^5)-...)
    // where the sign in front of pi/2 is + if x >=1 , - if x <= -1
    var res = 1/x;
    var add = 1/x;
    var last = NaN;
    for (var i = 3; res != last; i+= 2)
    {
        last = res;
        add *= -(i-2)/(x*x*i);
        res += add;
    }
    return (x < 0 ? -1 : 1) * Math.PI/2 - res;
};

// Ecma 15.8.2.5 atan2(y,x)
// Returns an implementation-dependent approximation to the arc tangent
// of the quotient y/x of the arguments y and x, where the signs of y
// and x are used to determine the quadrant of the result. Note that it is
// intentional and traditional for the two-argument arc tangent function
// that the argument named y be first and the argument named x be second.
// The result is expressed in radians and ranges from -pi to +pi
Esiply.set_attributes(Math, "atan2", Esiply.DONT_ENUM);
Math.atan2 = function(y, x)
{
    // First get the atan of (y/x)
    var res = atan(y/x);

    // if we're in quadrant I, add pi
    if (y > 0 && x < 0)
    {
        res += Math.PI;
    }

    // if we're in quadrand II, subtract pi
    if (y < 0 && x < 0)
    {
        res -= Math.PI;
    }

    // Otherwise we're okay.
    return res;
};

// Ecma 15.8.2.6 ceil(x)
// Returns the smallest (closest to -Infinity) number value that is not less
// than x and is equal to a mathematical integer. If x is already an
// integer, the result is x
// The value of Math.ceil(x) is the same as the value of -Math.floor(-x)
Esiply.set_attributes(Math, "ceil", Esiply.DONT_ENUM);
Math.ceil = function(x)
{
    return -Math.floor(-x);
};

// Ecma 15.8.2.7 cos(x)
// Returns an implementation-dependent approximation to the cosine of x.
// The argument is expressed in radians.
Esiply.set_attributes(Math, "cos", Esiply.DONT_ENUM);
Math.cos = function(x)
{
    // cos(x) is approximately
    // 1 - x^2/2! + x^4/4! - x^6/6! + ...
    var res = 1;
    var add = 1;
    var last = 0;
    for (var i = 2; res != last; i += 2)
    {
        last = res;
        add *= -x*x/(i * (i-1));
        res += add;
    }
    return res;
};

// Ecma 15.8.2.8 exp(x)
// Returns an implementation-dependent approximation to the exponential
// function of x (e raised to the poinwer of x, where e is the base of the
// natrual logarithms
Esiply.set_attributes(Math, "exp", Esiply.DONT_ENUM);
Math.exp = function(x)
{
    var res = 1;
    var add = 1;
    var last = 0;
    for (var i = 1; res != last; i++)
    {
        last = res;
        add *= x/i;
        res += add;
    }
    return res;
};

// Ecma 15.8.2.9 floor(x)
// Returns the greatest (closest to +Infinity) number value that is not
// greater than x and is equal to a mathematical integer. If x is already
// an integer, the result is x.
Esiply.set_attributes(Math, "floor", Esiply.DONT_ENUM);
Math.floor = function(x)
{
    x = (x < 0 && (x % 1) != 0) ? x-1 : x;
    return x - (x % 1);
};

// Ecma 15.8.2.10 log(x)
// Returns an implementation-dependant approximation to the natrual
// logarithm of x.
Esiply.set_attributes(Math, "log", Esiply.DONT_ENUM);
Math.log = function(x)
{
    // Boundary cases
    if (x < 0 || isNaN(x))
    {
        return NaN;
    }
    if (x == 0)
    {
        return -Infinity;
    }
    if (x == Infinity)
    {
        return Infinity;
    }

    // ln((1+a)/(1-a)) = 2a + 2/3*a^3 + 2/5*a^5 + ..., -1 < a < 1
    // We get a such that (1+a)/(1-a) is x, and then evaluting
    // the series gives an approximation for ln(x).
    var a = (x-1)/(1+x);
    var res = 2*a;
    var add = 2*a;
    var last = 0;
    for (var i = 3; res != last; i += 2)
    {
        last = res;
        add *= a*a;
        res += add/i;
    }
    return res;
};


// Ecma 15.8.2.11 max([value1[,value2[,...]]])
// Given zero or more arguments, calls toNumber on each of the arguments
// and returns the largets of the resulting values
// If no arguments are given, the result is -Infinity
// TODO: If any value is NaN, the result is NaN.
Esiply.set_attributes(Math, "max", Esiply.DONT_ENUM);
Math.max = function(x, y)
{
    var m = -Infinity;
    for (var i = 0; i < arguments.length; i++)
    {
        // Convert the argument to a number
        var arg = arguments[i] - 0;

        // If it's NaN, return NaN
        // TODO: once we've implemented isNaN

        // If m is smaller, we have a new max
        m = m < arg ? arg : m;
    }
    return m;
};

// Ecma 15.8.2.12 min([value1[,value2[,...]]])
// Given zero or more arguments, calls ToNumber on each of the 
// arguments and returns the smallest of the resulting values
// If no arguments are given, the result is +Infinity
// If any value is NaN, the result isNaN
Esiply.set_attributes(Math, "min", Esiply.DONT_ENUM);
Math.min = function(x, y)
{
    var m = Infinity;
    for (var i = 0; i < arguments.length; i++)
    {
        // Convert the argument to a number
        var arg = arguments[i] - 0;

        // If it's NaN, return NaN
        // TODO: once we've implemented isNaN

        // If m is bigger, we have a new min
        m = m > arg ? arg : m;
    }
    return m;
};

// Ecma 15.8.2.13 pow(x,y)
// Returns an implementation-dependent approximation to the result
// of raising x to the power y.
Esiply.set_attributes(Math, "pow", Esiply.DONT_ENUM);
Math.pow = function(x,y)
{
    // We want f = x^y. With some manipulation we get:
    // ln(f) = ln(x^y)
    // ln(f) = yln(x)
    // f = exp(yln(x)). And this we can already do
    return Math.exp(y * Math.log(x));
};

// Ecma 15.8.2.14 random()
// Returns a number value with positive sign, greater than or equal to 0 but
// less than 1, chosen randomly or pseudo randomly with approximately uniform
// distribution over that range, using an implementation-dependent algorithm or
// strategy. This function takes no arguments.
Esiply.set_attributes(Math, "random", Esiply.DONT_ENUM);
Math.random = function(){

    // TODO: it might be better if these were in hexidecimal, but as of the
    // time of writing this, we couldn't do hexidecimal numbers
    var m = 281474976710656;
    var a = 25214903917;
    var c = 11;
    var x = 1;
    return function() { 
        x = (a*x+c)%m;
        return x/m;
    };
}();

// Ecma 15.8.2.15 round(x)
// Returns the number value that is closest to x and is equal to a
// mathematical integer. If two integer
// number values are equally close to x, then the result is the number
// value that is closer to +Infinity. If x is already an integer, the
// result is x.
// The value of Math.round(x) is the same as the value of Math.floor(x+0.5),
// except when x is -0 or is less than 0 but greater than or equal to -0.5;
// for these cases Math.round(x) returns -0, but Math.floor(x+0.5) returns
// +0. TODO: catch that case.
Esiply.set_attributes(Math, "round", Esiply.DONT_ENUM);
Math.round = function(x)
{
    return Math.floor(x+0.5);
};

// Ecma 15.8.2.16 sin(x)
// Returns an implementation-dependent approximation to the sine
// of x. The argument is expressed in radians
Esiply.set_attributes(Math, "sin", Esiply.DONT_ENUM);
Math.sin = function(x)
{
    var res = x;
    var add = x;
    var last = 0;
    for (var i = 2; res != last; i += 2)
    {
        last = res;
        add *= -x*x/(i * (i+1));
        res += add;
    }
    return res;
};

// Ecma 15.8.2.17 sqrt(x)
// Returns an implementation-dependent approximation to the square
// root of x
// If x is NaN, the result is NaN
// If x is less than 0, the result is NaN
// If x is +0, the result is +0
// If x is -0, the result is -0
Esiply.set_attributes(Math, "sqrt", Esiply.DONT_ENUM);
Math.sqrt = function(x)
{
    // TODO: if (isNaN(x)) return NaN;
    if (x < 0) return NaN;
    if (x == 0 || x == Infinity) return x;
    
    var r = x;
    var last = 0;
    while (r != last)
    {
        last = r;
        r = (r + x/r)/2;
    }
    return r;
};

// Ecma 15.8.2.18 tan(x)
// Returns an implementation-dependent approximation to the tangent of x.
// The argument is expressed in radians.
Esiply.set_attributes(Math, "tan", Esiply.DONT_ENUM);
Math.tan = function(x)
{
    return Math.sin(x)/Math.cos(x);
};

